
講著 PHP 的 include,逐家加減攏會想著 LFI (Local File Inclusion) 這个老朋友。本來想講,這个古早味的漏縫欲耍 LFI to RCE 到 Solving "includer's revenge" from hxp ctf 2021 without controlling any files 彼改應該已經變無步矣,毋過咱「規世界上好的程式語言」PHP 原仔是會使變新齣頭予咱!這篇文章欲來紹介 DeadsecCTF2025 的一題「baby-web」,雖然這「欻度」佮 PHP filter chain 烏白隨咱生資料彼改袂比得,毋過總是算一个好耍的特性。
這題的 code 嘛算簡單,一隻 upload.php 處理檔案上傳,閣一隻 index.php 去共 include,
<?php
// index.php
$include_url = basename($_GET['url']);
$SANDBOX = ...;
// ... 一寡 sandbox 檢查/建立的 code ...
if (!file_exists($SANDBOX . '/' . $include_url)) die("Nope :<");
if (!preg_match("/\.(zip|bz2|gz|xz|7z)/i", $include_url)) die("Nope :<");
@include($SANDBOX . '/' . $include_url);
?>
<?php
// upload.php
$allowed_extensions = ['zip', 'bz2', 'gz', 'xz', '7z'];
$allowed_mime_types = [
'application/zip', 'application/x-bzip2', 'application/gzip',
'application/x-gzip', 'application/x-xz', 'application/x-7z-compressed',
];
function filter($tempfile) {
$data = file_get_contents($tempfile);
if (stripos($data, "__HALT_COMPILER();") !== false ||
stripos($data, "PK") !== false ||
stripos($data, "<?") !== false ||
stripos(strtolower($data), "<?php") !== false) {
return true;
}
return false;
}
// ... (後壁的上傳理路)
簡單講這个挑戰有 3 个重點:
zip, bz2, gz, xz, 7z 這幾種壓縮檔去裡。__HALT_COMPILER();、PK、<? 抑是 <?php 這款字,若有就失敗。index.php 會用 basename($_GET['url']) 來提著檔名,確保你袂使利用 ../ 來跳目錄,嘛袂使直接用 phar:// 這款的 wrapper。最後,伊會共你傳去裡的檔案 include 入來。include 一个壓縮檔是會使創啥?內容都無 <? 矣閣,是欲按怎走 PHP code?這就是這題上趣味的所在。
欲了解這个問題咱著愛共 PHP 的ia̋n-jín蓋掀開,看伊底層的 C source code 是按怎運作的。問題的關鍵,就佇 include 這个動作頂頭。
若 PHP 欲用 include 去共一个檔案印入來的時,伊會去叫一个叫做 compile_filename 的函式。這个函式會沓沓仔一步一步行,上尾仔才閣去叫著 phar_compile_file 來處理咱的 phar 檔案。
關鍵的 code 佇 phar_compile_file 內底遮:
if (strstr(ZSTR_VAL(file_handle->filename), ".phar") && !strstr(ZSTR_VAL(file_handle->filename), "://")) {
// ...
}
遮的 code 檢查傳入來的檔名敢有包含 .phar 這个字串。若有,伊就煞會共這个檔案當做是 phar archive 來處理,就算伊無用 phar:// wrapper 來引入。
閣較趣味的代誌發生佇後面的 phar_open_from_fp 這个函式。 這个函式的流程大概是按呢:
\x1f\x8b\x08)。若是,就共伊自動解壓縮,然後重新開始處理解壓縮了後的檔案。BZh)。若是,仝款共伊自動解壓縮。__HALT_COMPILER(); 這个 phar 檔案的標記。結論就是:若你 include 一个檔名/路徑有 .phar 的檔案(親像 my_file.phar.gz),PHP 的底層會自動判斷伊是 Gzip 壓縮檔,共伊解壓縮了後,才閣當做 phar 檔案來執行!
這个發現直接就共頭前的所有限制破解矣。咱會當:
exploit.phar 檔案。exploit.phar 壓縮做 exploit.phar.gz。.gz 檔案,內容早就無 <? 抑是 __HALT_COMPILER(); 這寡關鍵字,所以會當順利通過 upload.php 的檢查。include('exploit.phar.gz'),PHP 就會佇底層恬恬仔共伊解壓縮,才閣執行內底的惡意 code,造成 RCE!咱會記得講咱檔案名共焦需要有「.phar」這五字,include 就tsua̋nn會共彼个檔案當做 phar 處理、引入來。所以毋管講是:
./foo.phar.gzip
./foo.pharzzzzz
./foo.pharmeow/bar/baz.png
攏會當予 PHP 共當做 PHAR 處理!
咱愛來加讀寡 PHP source,予 PHP 閣再偉大 :D
CTF 講三工矣,明仔載咱就轉來繼續講一寡研究的物件囉